/**
 * @file wvf.ts wvf格式文件编译处理
 * @note wvf 文件格式说明
 * wvf 以 .wvf 为后缀名.文件类似于 html,但仅有三种顶级元素 <script> <template> <style>
 * 三种顶组元素 有一个重要属性 name, 当同一个文件中三种顶级元素的 name 属性相同时,编译器将会
 * 在 相同的 <script> <template> <style> 查找 相关属性并修改
 * @note 相关 typscript编译器使用方法见  https://bgithub.xyz/microsoft/TypeScript/wiki/Using-the-Compiler-API ts.createSourceFile
 * @note 相关网站 https://ts-ast-viewer.com https://astexplorer.net/
 * @note windows ts 文件被视为视频导致文件夹打开慢的解决办法：
 * HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\KindMap  .ts 值为video 改为 document
 * 删除 HKEY_CLASSES_ROOT\.ts
 */
import path from "node:path";
import fs from "node:fs";
import ts from "typescript";
import os from "node:os";
import {StringLiteralLike,
    ResolvedProjectReference,
    getModeForUsageLocation,
    ModuleResolutionCache,
    createModuleResolutionCache,resolveModuleName
    } from "typescript/lib/typescript";
import { HtmlTag,} from "./ast";
import {MIME} from "./mime";
import exp from "node:constants";

/**
 * 深度拷贝一个对象
 * @param obj 准备拷贝的对象
 * @param hash 
 * @returns 返回拷贝的对象，与原对象的所有数据无关，仅数据内容相同，对拷贝的数据修改不影响原对象。
 */
function deepCopy(obj:any, hash = new WeakMap()){
    if (typeof obj !== 'object' || obj === null) {
        return obj;
    }
    // 如果是日期或正则对象则直接返回一个新对象  
    if (obj instanceof Date) {
        return new Date(obj);
    }
    if (obj instanceof RegExp) {
        return new RegExp(obj);
    }
    // 如果hash中有这个对象，则直接返回hash中缓存的对象
    if (hash.has(obj)) {
        return hash.get(obj);
    }
    let newObj = new obj.constructor();
    hash.set(obj, newObj);
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = deepCopy(obj[key], hash);
        }
    }
    return newObj;
}
/**
 * resolves a sequence of paths or path segments into an absolute path. call path.resolve
 * @param paths 
 * @returns 
 */
function reslovePath(...paths: string[]){
    let p = path.resolve(...paths);
    if(os.platform() == "win32"){p = p.replaceAll('\\','/')}
    return p;
}
/**
 * 获取相对目录
 * @param from 
 * @param to 
 * @returns 
 */
function relativePath(from:string, to:string){
    let p = path.relative(from,to);
    if(os.platform() == "win32"){p = p.replaceAll('\\','/')}
    return p;
}

/**
 * wvf 配置接口
 */
interface WvfConfig{
    name?:string, // 项目名称, 默认值 "WvfProjet";
    mode?:"Debug"|"Release", // 项目生成性质，默认 "Debug"
    rootDir?:string, //项目源码根目录，默认值 path.reslove("./");
    files?:string[], // 项目源文件文件，应使用相对目录，处理方式:outHTML指定的文件:解析并复制到 outDir 之中; *.html 仅复制; *.wvf 解析并输出;*.ts 解析并输出;*.*仅复制
    entry:string,//应仅为 .wvf 文件，会按顺序解析并在 outHTML 中生成一个对应的标签，
    outDir?:string, // 项目输出根目录，默认值 path.reslove("./dist");
    outJS?:string, // 项目输出根js文件，应仅为一个js文件的文件名，默认值 path.reslove(outDir,"index.js"); 保存的是相对目录，所有 wvf 文件 js 将会默认编译进入此文件
    outCSS?:string,// 项目输出根css文件，应仅为一个css文件的文件名，默认值 path.reslove(outDir,"index.css"); 保存的是相对目录，所有 wvf 文件 css 将会默认编译进入此文件
    outHTML?:string,// 项目输出根 html 文件，应仅为一个html文件的文件名，，默认值 path.reslove(outDir,"index.html");
    public?:string, // 项目公共文件目录，在 path.reslove(rootDir,"public"); 下的文件均会复制到此目录下，如果是 Debug模式仅会 创建一个链接。默认值 path.reslove(outDir,"public");
    /**
     * 生成其它 html 文件
     */
    projects?:{ 
        name?:string, // projects 子项目 输出项的文件名
        files?:string[], // 源文件，如果有
        entry:string,//应仅为 .wvf 文件，会解析并在 outHTML 中生成一个对应的标签
        outJS?:string, // 项目输出根 js 文件，默认值 path.reslove(outDir,"index.js"); 保存的是相对目录，所有 wvf 文件 js 将会默认编译进入此文件
        outCSS?:string,// 项目输出根 css 文件，默认值 path.reslove(outDir,"index.css"); 保存的是相对目录，所有 wvf 文件 css 将会默认编译进入此文件
        outHTML?:string,// 项目输出根 html 文件，默认值 path.reslove(outDir,"index.html");
    }[];
    /**
     * Typescript 配置参数
     */
    options?:{
        target?:"ES3"|"ES5"|"ES2015"|"ES2016"|"ES2017"|"ES2018"|"ES2019"|"ES2020"|"ES2021"|"ES2022"|"ES2023"|"ES2024"|"ESNext"|"Latest", // 目标类型
        declaration?: boolean, // Generate .d.ts files from TypeScript and JavaScript files in your project.
        declarationMap?: boolean,  //Create sourcemaps for d.ts files
        declarationDir?:string, //Specify the output directory for generated declaration files. 
        sourceMap?: true, //Create source map files for emitted JavaScript files. 
        // strict?: true, //Enable all strict type-checking options.
    },
    /**
     * 解决编译时 import 库的问题
     */
    resolveImport?:{ //解决 import 库的问题
        [key:string]:{ // key 指 import xxx from "key" SyntaxKind.StringLiteral
            alias?:string, // 相当于 <script src= jquery-3.7.1.min.js></script> 加入 html页面后 在 window 实例（Web控制台 输入 window）下的 $ 或 jQuery
            js?:string, // 源文件,关联的js文件，会拷贝进去
            css?:string,
            files?:string[],
        }
    }
};

/**
 * 源文件类型
 */
enum SourceFileType{
    TS = 0,
    WVF = 1,
    JS = 2,
    DTS = 3,
    JSON = 4,
    UNKNOWN,
};

enum WvfCompileMode{
    Debug,
    Release,
};

/**
 * 编译过和中间数据 CompileIntermediateProcessData
 */
interface CIPD{
    type:SourceFileType, // 文件类型
    name:string, //编译的文件名
    canonicalFileName:string, 
    components?:{[name:string]:string}, // 本 wvf中的组件
    /**导入的符号名 */
    _import?:{[key:string]:string[]},
    /** 导出的符号名*/
    _export?:string[], // 导出的符号名
    ast?:HtmlTag, // 如果是 .wvf 文件 AstHtml.parse 返回的数据
    sourceFile?:ts.SourceFile, // typescript 编译的 ast 文件
    js?:string, // 编译好的 js 文件
    jsBefore?:string, //在js之前插入的 js 代码
    jsAfter?:string, //在js之后插入的 js 代码
    /**
     * 针对 wvf 文件的数据
     */
    _wvfData?:{[_class:string]:{
            /**
             * 检查标签中含有的字符串中如果有 {{ }} 结构的 对象保存在此
             */
            text?:{
                data_w:number, // 对应的标签所赋的 data-w 属性值
                ser:number, // 在标签中的序号
                variable:string[], // 包含的变量名
                strFun:string, // 最后生成的函数
            }[],
            /**
             * 事件类型
             */
            event?:{
                type:DocumentEventMap,
                data_w:number, // 对应的标签所赋的 data-w 属性值
                variable:string[], // 包含的变量名
                fun:string, // 最后生成的函数
            }
        }
    }
}

/**
 * wvf 项目编译器
 */
class Wvf{
    /**
     * 读文件函数，
     */
    public static readFile = ts.sys.readFile;  
    private mode:WvfCompileMode; //项目编译模式 WvfCompileMode
    private rootDir:string; // 项目源码根目录
    private outDir:string; // 项目输出根目录
    private outJS:string; // 项目根输出 js 文件，所有 html将会包含此 js
    private outCSS:string; // 项目根输出 css 文件，所有 html将会包含此 css
    private outHTML:string; // 项目根输出 html 文件，所有 html将会包含此 css
    private entry:string; //入口文件
    private public:string;
    private files?:string[];
    private projects?:{ 
        name?:string, // projects 子项目 输出项的文件名
        files?:string[], // 源文件，如果有
        entry:string,//应仅为 .wvf 文件，会解析并在 outHTML 中生成一个对应的标签
        outJS?:string, // 项目输出根 js 文件，默认值 path.reslove(outDir,"index.js"); 保存的是相对目录，所有 wvf 文件 js 将会默认编译进入此文件
        outCSS?:string,// 项目输出根 css 文件，默认值 path.reslove(outDir,"index.css"); 保存的是相对目录，所有 wvf 文件 css 将会默认编译进入此文件
        outHTML?:string,// 项目输出根 html 文件，默认值 path.reslove(outDir,"index.html");
    }[];
    resolveImport?:Map< //解决 import 库的问题
        string,{ // key 指 import xxx from "key" SyntaxKind.StringLiteral
            alias?:string, // 相当于 <script src= jquery-3.7.1.min.js></script> 加入 html页面后 在 window 实例（Web控制台 输入 window）下的 $ 或 jQuery
            js?:string, // 源文件,关联的js文件，会拷贝进去
            css?:string,
            files?:string[],
        }>;
    private options:ts.CompilerOptions;
    private host:ts.CompilerHost;
    private program?:ts.Program;
    private diagnostics?:ts.Diagnostic[];
    private printer:ts.Printer;
    /**
     * 用于编译字符串
     */
    private cs:{ 
        host:ts.CompilerHost,
        src?:string|undefined,
        dist?:string|undefined,
    };
    /**
     * 编译中间过程数据 CompileIntermediateProcessData s
     */
    private cipd:Map<string ,CIPD>;
    /**
     * 编译中间过程数据缓存 CompileIntermediateProcessData s
     */
    private cipdCache:Map<string ,CIPD>;
    /**
     * 所有从 wvf 文件中导入的 组件对象 Map<_className:_tagName>
     */
    private components:Map<string/**_className */,string/**_tagName */>;
    private _moduleResolutionCache: ModuleResolutionCache;//用于加速编译 // 此为处理import所用的cache (this.host as any).resolveModuleNameLiterals
    constructor(config:WvfConfig){
        {
            let cwd = process.cwd();
            this.mode = {"Debug":WvfCompileMode.Debug,"Release":WvfCompileMode.Release}[config.mode as unknown as string]??WvfCompileMode.Debug; //设置编译模式
            this.rootDir = reslovePath(cwd, config?.rootDir??"./"); //  设置 项目源码目录
            this.outDir = reslovePath(cwd, config?.outDir??"./dist"); // 设置项目编译输出目录
            this.outJS = config.outJS??"index.js";
            this.outCSS = config.outCSS??"index.css";
            this.outHTML = config.outHTML??"index.html";
            if(config.files) this.files = config.files;
            this.entry = reslovePath(this.rootDir,config.entry);
            this.public = reslovePath(this.outDir, config.public??"public");  
            if(config.projects){this.projects = config.projects;}    
            if(config.resolveImport){
                this.resolveImport = new Map;
                for (let key in config.resolveImport){
                    let v:{alias?:string,js?:string,css?:string,files?:string[]} = { };
                    if(config.resolveImport[key].alias){ v.alias = config.resolveImport[key].alias; }
                    if(config.resolveImport[key].js){ v.alias = this.resloveImportPath(config.resolveImport[key].js);}
                    if(config.resolveImport[key].css){ v.alias = this.resloveImportPath(config.resolveImport[key].css);}
                    if(config.resolveImport[key].files){
                        v.files =[];
                        for(let i = 0; i != config.resolveImport[key].files.length; i++){
                            v.files.push( this.resloveImportPath(config.resolveImport[key].files[i]));
                        }
                    }
                    this.resolveImport.set(key,v);
                }
            }  
        }
        // 配置编译选项
        {
            this.options = {
                target:ts.ScriptTarget.ESNext, 
                module:ts.ModuleKind.ESNext,
                moduleResolution:ts.ModuleResolutionKind.Bundler,
                allowImportingTsExtensions:true,
                newLine:ts.NewLineKind.LineFeed,
                removeComments:true,
                // strict:true,
                allowJs:true,
            } as ts.CompilerOptions
            if(config.options){
                let options = config.options;
                this.options.target = {"ES3":ts.ScriptTarget.ES3,"ES5":ts.ScriptTarget.ES5,"ES2015":ts.ScriptTarget.ES2015,
                                        "ES2016":ts.ScriptTarget.ES2016,"ES2017":ts.ScriptTarget.ES2017,"ES2018":ts.ScriptTarget.ES2018,
                                        "ES2019":ts.ScriptTarget.ES2019,"ES2020":ts.ScriptTarget.ES2020,"ES2021":ts.ScriptTarget.ES2021,
                                        "ES2022":ts.ScriptTarget.ES2022,"ES2023":ts.ScriptTarget.ES2023,"ES2024":ts.ScriptTarget.ES2024,
                                        "ESNext":ts.ScriptTarget.ESNext,"Latest":ts.ScriptTarget.Latest,}[options.target as any as "Latest"]??ts.ScriptTarget.Latest;
                if(options.declaration ) this.options.declaration = options.declaration;
                if(options.declarationMap)  this.options.declarationMap = options.declarationMap;
                if(options.declarationDir) this.options.declarationDir = options.declarationDir;
                if(options.sourceMap) this.options.sourceMap = options.sourceMap;
                // if(options.strict) this.options.strict = options.strict;
            }            
        }
        this.cipd = new Map; this.cipdCache = new Map;this.components = new Map;
        this.cs = {host:ts.createCompilerHost(this.options), } //用于编译字符串
        this.cs.host.readFile = ()=>this.cs.src; this.cs.host.writeFile = (fileName,text)=>{this.cs.dist = text}; 
        let gcf = this.cs.host.getCanonicalFileName;
        this.cs.host.getCanonicalFileName = (fileName: string):string =>{return this.cipdCache.get(fileName)?.canonicalFileName ?? gcf(fileName);}
        // let getDefaultLibFileName =  (options: ts.CompilerOptions): string => 'lib.d.ts';
        // this.cs.host.getDefaultLibFileName =getDefaultLibFileName
        // 创建用于编译的 CompilerHost
        this.host = ts.createCompilerHost(this.options); // 创建一个 ts.CompilerHost 用于传入 ts.createProgram
        let getCanonicalFileName = this.host.getCanonicalFileName,writeFile=this.host.writeFile;
        // this.host.getDefaultLibFileName = getDefaultLibFileName;
        this.host.getCanonicalFileName = (fileName: string):string =>{ //检查文件类型
            let c = this.cipdCache.get(fileName); // 检查编译中间过程数据中有没有
            if(c){
                return c.canonicalFileName;
            }
            const E = {
                ".ts":SourceFileType.TS,
                ".wvf":SourceFileType.WVF,
                ".js":SourceFileType.JS,
                ".json":SourceFileType.JSON
            };
            let v = {
                name:fileName, canonicalFileName:getCanonicalFileName(fileName),
                type:E[path.extname(fileName) as ".ts"]??SourceFileType.UNKNOWN,
            } as CIPD;
            if(v.type == SourceFileType.TS){
                let f = path.basename(fileName).toLowerCase();
                if(f.endsWith(".d.ts")){v.type = SourceFileType.DTS;}
            }
            else if(v.type == SourceFileType.WVF){
                v.canonicalFileName = fileName + ".ts";
                v._wvfData = {}; 
            }else if(v.type == SourceFileType.JS){
                let f = path.basename(fileName).toLowerCase();
                if(f.endsWith(".wvf.js")){v.type = SourceFileType.UNKNOWN;}
            }
            this.cipdCache.set(fileName,v);
            if([SourceFileType.TS,SourceFileType.WVF,SourceFileType.JS].includes(v.type) ){this.cipd.set(fileName,v);}
            return v.canonicalFileName;
        }
        // 解决文件读取时分类问题
        this.host.readFile = (fileName: string):string|undefined =>{
            let c = this.cipdCache.get(fileName);// 检查编译中间过程数据中有没有
            if(c?.type == SourceFileType.WVF){
                let txt = Wvf.readFile(fileName);
                if(txt){
                    let ast = HtmlTag.fromString(txt);
                    if(ast){
                        c.ast = ast;
                        return ast.script();
                    }
                }
            }
            return Wvf.readFile(fileName);
        }
        // 缓存数据， 加速 resolveModuleNameLiterals 调用
        this._moduleResolutionCache = createModuleResolutionCache(this.host.getCurrentDirectory(), getCanonicalFileName, this.options as any); //加速编译
        // this.host.resolveModuleNames = (moduleNames: string[], containingFile: string): ts.ResolvedModule[] =>{ }
        // 解决模块导入问题
        (this.host as any).resolveModuleNameLiterals = (
                moduleLiterals: readonly StringLiteralLike[],
                containingFile: string,
                redirectedReference: ResolvedProjectReference | undefined,
                options: ts.CompilerOptions,
                containingSourceFile: ts.SourceFile,
                reusedNames: readonly StringLiteralLike[] | undefined,
            ): readonly ts.ResolvedModuleWithFailedLookupLocations[] => {
            if (moduleLiterals.length === 0) return [] as never[];
            const resolutions:ts.ResolvedModuleWithFailedLookupLocations[] = [];
            const cache = new Map<string & { __modeAwareCacheKey: any; }, ts.ResolvedModuleWithFailedLookupLocations>();
            for (const entry of moduleLiterals) {
                const name = entry.text;
                const mode = getModeForUsageLocation(containingSourceFile as any,entry,options as any);
                const key = (mode === undefined ? name : `${mode}|${name}`) as string & { __modeAwareCacheKey: any; };
                let result:ts.ResolvedModuleWithFailedLookupLocations = cache.get(key) as ts.ResolvedModuleWithFailedLookupLocations;
                if (!result) {
                    result= (path.extname(name) == ".wvf")? ({
                            affectingLocations: undefined,
                            alternateResult:undefined,
                            failedLookupLocations:undefined,
                            resolutionDiagnostics:undefined,
                            resolvedModule:{
                                extension:".ts",
                                isExternalLibraryImport:false,
                                originalPath:undefined,
                                packageId:undefined,
                                resolvedFileName: reslovePath(this.rootDir,name),
                                resolvedUsingTsExtension:false,
                            }
                        } as ts.ResolvedModuleWithFailedLookupLocations) :
                        resolveModuleName(name,
                        containingFile,
                        options as any,
                        this.host,
                        this._moduleResolutionCache,
                        redirectedReference,
                        mode,)as unknown as ts.ResolvedModuleWithFailedLookupLocations;
                    cache.set(key, result);
                }
                resolutions.push(result);
            }
            return resolutions;
        }
        //解决写入最终文件的问题
        this.host.writeFile = (fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void): void =>{
            if(fileName.endsWith(".wvf.js")){
                let wvf = this.cipd.get(fileName.substring(0,fileName.length-3));
                if(wvf){  wvf.js = data/*.replace('"use strict"',"")*/; }
            }else if((fileName.endsWith(".ts")&& (!fileName.endsWith(".d.ts"))) || fileName.endsWith(".js")){
                let wvf = this.cipd.get(fileName);
                if(wvf){wvf.js = data/*.replace('"use strict"',"")*/;}
            }/*else {
                this.runtime.compiler.writeFile(fileName,data,writeByteOrderMark,onError);
            }*/
        }
        this.printer = ts.createPrinter(/*{ newLine: ts.NewLineKind.LineFeed}*/);
    }
    static node_modules:string[] = (():string[]=>{
        let nm:string[] = [...(require.main as unknown as {paths:string[]}).paths,
            reslovePath(path.dirname(process.execPath),"node_modules"),
            "/usr/local/lib/node_modules",
            "/usr/lib/node_modules",
            "/opt/node/lib/node_modules",
        ];
        for(let i = 0; i != nm.length;){
            try{
                fs.accessSync(nm[i], fs.constants.W_OK|fs.constants.R_OK );
            }catch(err){
                nm.splice(i,1);continue;
            };
            i++;
        }
        return nm;
    })();
    public resloveImportPath (p:string):string{
        if(path.isAbsolute(p)){
            return reslovePath(p);
        }else if(p.startsWith("./") || p.startsWith("../") || p.startsWith(".\\")|| p.startsWith("..\\")){
            return reslovePath(this.rootDir, p);
        }
        try{
            let r =  reslovePath(this.rootDir,p);
            fs.accessSync(r, fs.constants.R_OK );
            return r;
        }catch(err){ };  
        if(p.startsWith("node_modules/")){p = p.substring(13);}
        for(let i = 0; i != Wvf.node_modules.length; i++){
            let r =  reslovePath(Wvf.node_modules[i],p);
            try{
                fs.accessSync(r, fs.constants.R_OK );
                return r;
            }catch(err){ };
        }
        return reslovePath(this.rootDir, p);
        
    }
    /**
     * 打印 node 下的代码
     * @param sourceFile 
     * @param node 
     */
    private printSections(sourceFile:ts.SourceFile, node:ts.Node):string{
        return this.printer.printNode(ts.EmitHint.Unspecified, node, sourceFile);
    }
    private static _TMPFILE = "temp.ts";// 编译单独字符串时的文件名
    /**
     * 编译 wvf 文件中的事件的字符串源码
     * @param src 
     * @param id 
     * @returns 编译完成后的字符串
     */
    compileWvf(src:string, id:number, processSourceFile?:(sourceFile:ts.SourceFile)=>void ):string|undefined{
        this.cs.src = src;
        this.cs.dist = undefined;
        let program = ts.createProgram([Wvf._TMPFILE],this.options, this.host, this.program);
        if(processSourceFile){
            let sourceFile = program.getSourceFile(Wvf._TMPFILE);
            if(sourceFile){processSourceFile(sourceFile);}       
        }
        let before = "", after = "";
        if(id !==  undefined){
            before = "function(){";
            after = "}";
            let diagnostics = ts.getPreEmitDiagnostics(program) as ts.Diagnostic[];
            if(diagnostics){
                for(let i =0;i != diagnostics?.length;i++){
                    let d = diagnostics[i];
                    if(d.code == 2304 && d.file?.fileName == Wvf._TMPFILE){
                        let v = this.cs.src.substring(d.start as number, (d.start  as number) + (d.length  as number));
                        before += (v != "data") ? `let ${v} = this.data.${v};` : "let data = this.data;";
                    }
                }
            }
        }
        this.diagnostics = program.emit().diagnostics as ts.Diagnostic[];
        if(this.program === undefined) this.program = program;
        return before + this.cs.dist + after;
    }
    /**
     * 编译 字符串源码
     * @param str 
     * @returns 
     */
    compileString(str:string, processSourceFile?:(sourceFile:ts.SourceFile,params?:any)=>void, params?:any):string|undefined{
        this.cs.src = str; this.cs.dist = undefined; this.diagnostics = undefined;
        let program = ts.createProgram([Wvf._TMPFILE],this.options, this.cs.host, this.program);
        if(processSourceFile){
            let sourceFile = program.getSourceFile(Wvf._TMPFILE);
            if(sourceFile){processSourceFile(sourceFile, params);}       
        }
        this.diagnostics = program.emit().diagnostics as ts.Diagnostic[];
        if(this.program === undefined) this.program = program;
        return this.cs.dist;
    }
    /**
     * 仅编译一个文件，文件中不应有 import export
     * @param fileName 
     * @returns 
     */
    compileFile(fileName:string):string|undefined{
        let t = Wvf.readFile(fileName); // 现在处于测试阶段，数据填成这样
        if(t){  return this.compileString(t); }
    }
    /**
     * 获取编译结果信息
     * @returns 
     */
    getDiagnostics(){
        return this.diagnostics;
    }
    /**
     * 执行项目的编译
     * @param _file 
     */
    compile(){console.log(process.cwd());
        let sources:string[] = [this.entry]; // 把所有 .ts .wvf .js 文件集中到这里
        let preJS = "\"use strict\";\nvar WVF={\n"; // 保存一些数据写入 index.js
        let program = ts.createProgram(sources, this.options,this.host, this.program);
        if(this.program === undefined) this.program = program;
        // 初步处理
        program.getSourceFiles().forEach((sourceFile:ts.SourceFile,index:number)=>{
            let wvf = this.cipd.get(sourceFile.fileName);
            if(wvf && [SourceFileType.WVF,SourceFileType.TS,SourceFileType.JS].includes(wvf.type)){
                wvf.sourceFile = sourceFile;
                this.processImportexport(sourceFile,wvf); 
            }
        });
        // 统计wvf 使用的 组件名
        this.cipd.forEach((wvf)=>{
            if(SourceFileType.WVF == wvf.type){ 
                let components:{[name:string]:string}={};
                wvf.ast?.forEach((child,i,ch)=>{
                    if(child.type == "script"){
                        let name = child.getAttr("name");
                        if(name){ components[name] = `w-${name.toLowerCase()}`; }
                    }
                });
                if(Object.keys(components).length){
                    wvf.components = components; for(let k in components){this.components.set(k, components[k]);}
                }
            }
        })
        // 精细处理 wvf
        let ID = 0;
        this.cipd.forEach((wvf)=>{
            if(SourceFileType.WVF == wvf.type){ 
                wvf.ast?.forEach((child,i,ch)=>{
                    if(child.type == "template"){
                        let _className =  child.getAttr("name") as string; 
                        if(_className){
                            let JS=`\t${_className}:{\n\t\tref:[\n\t\t\t`; //此 标签的代码
                            child.forEachDescendant((descendant,index,level)=>{
                                 // 处理 响应式标签
                                if(descendant.type== "#text"){ // 标签为 Text
                                    const reg = /(?:{{)(.*?)(?:}})/g;  // 检查是否是 响应式标签 前后 {{ }} 使用非获取匹配
                                    if((!["script","style"].includes(descendant.parent.type as "script")) && // 标签不是 <script> 或 <style> 的 子标签
                                        descendant.value?.length && (-1 != descendant.value.search(reg)) /* 标签 存在值 并且其中含有 {{ }} 的字符串*/){
                                        let JS1 = "";
                                        // 设置 相关元素的 data-w
                                        let attr = descendant.parent.getAttr("data-w"); 
                                        if(!attr){attr =(ID++).toString(); descendant.parent.setAttr("data-w", attr);}
                                        let eaes:string[] = [];  // ElementAccessExpressions 保存含有 this.data 的变量
                                        let tstring = `function(){return \`${descendant.value.replaceAll(reg,"${$1}")}\`;}`; // 模板字符串 用 `` 括起来的字符串
                                        tstring = this.compileString(tstring,(sourceFile: ts.SourceFile, params?: any)=>{
                                            /**
                                             * 修改 this.data 为 this._data
                                             * @param node 
                                             */
                                            const delintNode3 = (node: ts.Node) => {
                                                if (node.kind == ts.SyntaxKind.PropertyAccessExpression && 
                                                    ((node as any).expression.kind == ts.SyntaxKind.ThisKeyword) &&
                                                    (node as any).name.kind == ts.SyntaxKind.Identifier && (node as any).name.escapedText == "data") {
                                                        (node as any).name = ts.factory.createIdentifier("_data");
                                                }
                                                ts.forEachChild(node, delintNode3);
                                            }
                                            delintNode3(sourceFile);
                                            //遍历 sourceFile 查找 this.data
                                            const delintNode =(node: ts.Node) => {
                                                if (node.kind == ts.SyntaxKind.PropertyAccessExpression) {
                                                    let expr = this.printSections(sourceFile,node);
                                                    if(expr.startsWith("this._data")){
                                                        eaes.push(expr); 
                                                        if(expr.indexOf("this._data",10) != -1){
                                                            /**
                                                             * 查找 数组访问
                                                             * @param node 
                                                             * @returns 
                                                             */
                                                            const delintNode2 = (node: ts.Node) => {
                                                                if(node.kind == ts.SyntaxKind.ElementAccessExpression){
                                                                    delintNode((node as any).argumentExpression as ts.Node);
                                                                    return;
                                                                }
                                                                ts.forEachChild(node, delintNode2);
                                                            }
                                                            ts.forEachChild(node, delintNode2);
                                                        }
                                                        return;
                                                    }
                                                }
                                                ts.forEachChild(node, delintNode);
                                            }
                                            delintNode(sourceFile);
                                        }) as string;
                                        for(let i = 0; i != eaes.length; i++){
                                            JS1 += `function(){${eaes[i]};},`;
                                        }
                                        JS1 = `{_vars:[${JS1}],${attr}:{ser:${index},fun:${tstring},},},\n`;
                                        JS += JS1 + "],\n";
                                    }
                                    return;
                                }
                                // 将 template 内部 导入 wvf 中的标签改为 w- 格式的标签
                                this.components.forEach((_tagName:string,_className:string)=>{
                                    if(descendant.type == _className){ descendant.type = _tagName; }
                                });
                                // 遍历 标签属性
                                descendant.forEachAttr((attr,value)=>{
                                    switch(attr){
                                        case "w-text":{
                                            // see https://cn.vuejs.org/api/built-in-directives.html#v-text
                                        }break;
                                        case "w-html":{
                                            // see https://cn.vuejs.org/api/built-in-directives.html#v-html
                                        }break;
                                        case "w-show":{
                                            // see https://cn.vuejs.org/api/built-in-directives.html#v-show
                                        }break;
                                        case "w-if":{
                                            // see https://cn.vuejs.org/api/built-in-directives.html#v-if
                                        }break;
                                        case "w-else":{
                                            // see https://cn.vuejs.org/api/built-in-directives.html#v-else
                                        }break;
                                        case "w-elif":{
                                            // see https://cn.vuejs.org/api/built-in-directives.html#v-else-if
                                        }break;
                                        case "w-for":{
                                            // see https://cn.vuejs.org/api/built-in-directives.html#v-for
                                        }break;
                                        case "w-model":{
                                            // see https://cn.vuejs.org/api/built-in-directives.html#v-model
                                        }break;
                                        case "w-slot":{
                                            // see https://cn.vuejs.org/api/built-in-directives.html#v-slot
                                        }break;
                                        case "v-pre":{
                                            // see https://cn.vuejs.org/api/built-in-directives.html#v-pre
                                        }break;
                                        case "v-once":{
                                            // see https://cn.vuejs.org/api/built-in-directives.html#v-once
                                        }break;
                                        default:{
                                            if(attr.startsWith("w-bind:") || attr.startsWith(":")){ 
                                                // 处理 w-bind: 或 : 动态的绑定一个或多个 attribute  参考 https://cn.vuejs.org/api/built-in-directives.html#v-bind
                                                
                                            } else if(attr.startsWith("w-on:") || attr.startsWith("@") ){
                                                let ev = attr.substring(5,)?.split('.');
                                                if(ev){
                                                    switch(ev[0]){
                                                        case "click":{
                    
                                                        }break;
                                                        case "keydown":
                                                        case "keypress":
                                                        case "keyup":{
                    
                                                        }break;
                                                    }
                                                }
                                            }
                                        }break;
                                    }
                                });
                            });
                            JS+="},\n"; preJS += JS;
                        }
                    } else if(child.type == "style" && child.getAttr("name")?.length && child.children[0]?.value?.length){
                        this.components.forEach((_tagName,_className)=>{
                            // @ts-ignore
                            child.children[0].value = child.children[0].value.replaceAll(_className,_tagName); // css 先这样处理
                        });
                    }
                });
            }
        });
        this.diagnostics = program.emit().diagnostics as ts.Diagnostic[];
        preJS += "};\n";
        // 将相关文件写入 生成目录
        // 写入js
        try{
            let fdjs = fs.openSync(path.resolve(this.outDir,this.outJS) ,"w+");
            fs.ftruncateSync(fdjs,0);
            fs.writeSync(fdjs, preJS);
            let s = this.compileFile("../src/lang-wvf/wvf-dist.ts");
            if(s){ fs.writeSync(fdjs, s); }
            if(this.resolveImport){
                for(const [fileName, value] of this.resolveImport ){
                    if(value.js){
                        let data = fs.readFileSync(value.js,{flag:"r"});
                        if(data){
                            fs.writeSync(fdjs, data as any as string);
                            fs.writeSync(fdjs, ";");
                        }
                    }
                }
            }
            this.cipd.forEach((wvf:CIPD)=>{
                if(wvf.jsBefore || wvf.js || wvf.jsAfter){
                    fs.writeSync(fdjs, "{");
                    if(wvf.jsBefore){fs.writeSync(fdjs, wvf.jsBefore );}
                    if(wvf.js){fs.writeSync(fdjs, wvf.js );}
                    if(wvf.jsAfter){fs.writeSync(fdjs, wvf.jsAfter );}
                    fs.writeSync(fdjs, "}");
                }
            });
            fs.closeSync(fdjs);
        }catch(err){
            console.log(err);
        }
        //写入css
        try{
            let fdcss = fs.openSync(path.resolve(this.outDir,this.outCSS),"w+");
            fs.ftruncateSync(fdcss,0);
            if(this.resolveImport)for(const [fileName, value] of this.resolveImport ){
                if(value.css){
                    let data = fs.readFileSync(value.css,{flag:"rb"});
                    if(data){ fs.writeSync(fdcss, data as any as string);}
                }
            }
            for(const [fileName, wvf] of this.cipd ){
                if(wvf.ast){fs.writeSync(fdcss, wvf.ast.css());}
            }
            fs.closeSync(fdcss);
        }catch(err){
            console.log(err);
        }
        //写入 html
        let fdhtml = -1; // html 文件描述符
        try{
            fdhtml = fs.openSync(path.resolve(this.outDir as string,this.outHTML),"w+");
        }catch(err){
            console.log(err);
        }
        let ast = HtmlTag.fromString('<html lang="zh-CN"><head><meta charset="utf-8"/></head><body></body></html>') as HtmlTag;
        let html = ast.queryTag("html",undefined) as HtmlTag;
        let head = html.queryTag("head",undefined) as HtmlTag;
        let body = html.queryTag("body",undefined) as HtmlTag;
        head.addChild( "link",  {href:path.basename(this.outCSS as string),rel:"stylesheet"});
        // 处理 body
        let script = body.addChild( "script", {src:path.basename(this.outJS as string),async:undefined});
        let div = body.addChild("div",{id:"wvf-template"});
        this.cipd.forEach((wvf,name:string)=>{
            if(wvf.type == SourceFileType.WVF){
                //@ts-ignore
                let template = wvf.ast.template();
                if(template){
                    let t = div.addChild("#text", undefined);
                    t.value = template;
                };
            }
        });
        // 添加入口文件
        let entry = this.cipd.get((this.entry as string).replaceAll( '\\','/'));
        if(entry && entry.components){
            for(let k in entry.components){ body.addChild(entry.components[k],undefined); }
        }
        try{
            fs.writeSync(fdhtml, '<!DOCTYPE html>' + ast.toString("  "));
            fs.closeSync(fdhtml);
        }catch(err){
            console.log(err);
        }
    }
    /**
     * 删除文档中的import/export,并处理删除可以造成的错误
     * @param sourceFile 
     * @param wvf 
     */
    private processImportexport(sourceFile:ts.SourceFile, wvf:CIPD) {
        /**
         * 向 wvf.jsBefore 后追加代码
         * @param wvf 
         * @param s 
         */
        const ajb = (wvf:CIPD, s:string )=>{ if(wvf.jsBefore){wvf.jsBefore+=s;}else{wvf.jsBefore=s;} }
        /**
         * 向 wvf.jsAfter 后追加代码
         * @param wvf 
         * @param s 
         */
        const aja = (wvf:CIPD, s:string )=>{ if(wvf.jsAfter){wvf.jsAfter+=s;}else{wvf.jsAfter=s;} }
        /**
         * 把 export 关键字 保存在 wvf._export 之中
         * @param name 
         * @param nameas 
         */
        const ae = (wvf:CIPD, name:string)=>{ if(wvf._export){wvf._export.push(name);}else{wvf._export=[name];} }
        /** 把 import 关键字 保存在 wvf._import 之中 */
        const ai = (wvf:CIPD,moduleName:string, name:string)=>{
            if(wvf._import === undefined){wvf._import = {};}
            if(wvf._import[moduleName] === undefined) {wvf._import[moduleName] = [];}
            wvf._import[moduleName].push(name);
        }
        /**
         * 从 node 删除 export 关键字 ,返回 export 的变量列表 把 export 的数据 写入 wvf.jsAfter
         * @param node 
         */
        const df = (wvf:CIPD,vnode:ts.VariableStatement|ts.FunctionDeclaration|ts.ClassDeclaration)=>{
            if(vnode.modifiers && (vnode.modifiers as any as ts.ExportKeyword[])[0].kind == ts.SyntaxKind.ExportKeyword){ // export 关键字必定在第一个位置
                if(vnode.modifiers.length >= 1){
                    (vnode.modifiers as any as ts.ExportKeyword[]).splice(0,((vnode.modifiers as any as ts.DefaultKeyword[]).at(2)?.kind == ts.SyntaxKind.DefaultKeyword)?2:1); 
                }
                if(vnode.modifiers.length == 0) { (vnode.modifiers as any) = undefined; }
                if(vnode.kind == ts.SyntaxKind.VariableStatement){ // export let a,b,c; 或export var a,b,c;或export const a,b,c;
                    vnode.declarationList.declarations?.forEach((vdnode:ts.VariableDeclaration)=>{
                        let name = (vdnode.name as ts.Identifier).escapedText as string;
                        ae(wvf,name); aja(wvf, `window.${name}=${name};`);
                    } );                    
                } else if(vnode.name ){ // export function f(){} 或 export class c{}
                    let name = vnode.name.escapedText as string;
                    ae(wvf,name);aja(wvf, `window.${name}=${name};`);
                }
            }
        }
        let _nodeDel:(ts.ImportDeclaration|ts.ExportAssignment|ts.ExportDeclaration)[] = []; // 保存 import / export 节点后面用于删除
        let identifiers = new Set<string>; // import 的变量名，也需要删除
        // import / export 只能置于顶级，所以只需要循环检查顶级对象即可
        sourceFile.forEachChild( ((node: ts.ImportDeclaration|ts.VariableStatement|ts.FunctionDeclaration|ts.ClassDeclaration|ts.ExportDeclaration|ts.ExportAssignment)=>{
            switch(node.kind){
                case ts.SyntaxKind.ImportDeclaration:{
                    _nodeDel.push(node as ts.ImportDeclaration);
                    //https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/import
                    // import 声明有四种形式：
                    // 具名导入：import { export1, export2 } from "module-name";
                    // 默认导入：import defaultExport from "module-name";
                    // 命名空间导入 ：import * as name from "module-name";
                    // 副作用导入：import "module-name";
                    let moduleName = (node.moduleSpecifier as ts.StringLiteral).text; // 模块名称肯定存在，不用判断 ,例：import "About.wvf"
                    if(node.importClause){ // 具名导入 / 默认导入 / 命名空间导入
                        if(node.importClause.namedBindings){
                            let namedBindings = node.importClause.namedBindings; 
                            if(namedBindings.kind == ts.SyntaxKind.NamedImports){ // 具名导入
                                for(let i =0; i !=  namedBindings.elements?.length; i++){
                                    let element = namedBindings.elements[i] as ts.ImportSpecifier;
                                    let name:string,named:string; 
                                    if(element.propertyName) {
                                        name = (element.propertyName as ts.Identifier).escapedText as string;
                                        named = element.name.escapedText as string;
                                        identifiers.add(name);identifiers.add(named);
                                        if(name != "default"){ ajb(wvf, `let ${named} = ${name};`); ai(wvf, moduleName, name);}
                                    }
                                    else{
                                        name = element.name.escapedText as string;identifiers.add(name);ai(wvf, moduleName, name);
                                    }
                                }
                            }else if(namedBindings.kind == ts.SyntaxKind.NamespaceImport){ // 命名空间导入
                                let name = namedBindings.name.escapedText as string; identifiers.add(name); ai(wvf,moduleName,  "* " + name);// 导入的名称
                            }
                        }
                         if(node.importClause.name){  //默认导入  
                            let name = node.importClause.name.escapedText as string;identifiers.add(name);ai(wvf,moduleName,  "*");
                        }
                    }/*else{
                        // side effect import// 未完成功能，不要删除注释，
                    }*/
                }   break;
                case ts.SyntaxKind.VariableStatement:case ts.SyntaxKind.FunctionDeclaration:case ts.SyntaxKind.ClassDeclaration:
                    // 导出声明  变量/函数/类声明，let/var/const/function/class 检查是否有 exports    
                    //https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/export
                    df(wvf,node);
                    break;
                case ts.SyntaxKind.ExportAssignment:{  // Default exports 例：let aaa = 1;  export default aaa;
                    _nodeDel.push();
                    let name = (node.expression as ts.Identifier).escapedText as string ;
                    ae(wvf,name); aja(wvf,name); 
                }   break;
                case ts.SyntaxKind.ExportDeclaration:{ // 单独的 export 声明
                    _nodeDel.push(node);
                    //https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Statements/export
                    //// 导出列表
                    // 默认导出
                    // 导出模块合集 不处理
                    (node.exportClause as ts.NamedExports)?.elements.forEach((enode:ts.ExportSpecifier)=>{ // 处理导出列表
                        let t:string;
                        if(enode.propertyName){
                            let name = (enode.propertyName as ts.Identifier).escapedText as string;
                            let named = (enode.name as ts.Identifier).escapedText as string; 
                            identifiers.add(named);
                            ae(wvf, name);
                            t =( named == "default" ? `window.${name}=${name};` : `window.${named}=${name};`);
                        }else{
                            let name = (enode.name as ts.Identifier).escapedText as string;
                            identifiers.add(name);
                            t = `window.${name}=${name};`; 
                            ae(wvf, name);
                        }
                        aja(wvf,t);
                    });
                }break;
            }
        }) as (node: ts.Node) => void | undefined);
        // 根据记录删除 import
        for(let i =0; i != _nodeDel.length; i++){
            for (let j = 0; j != sourceFile.statements.length; j++) {
                if (sourceFile.statements[j] == _nodeDel[i]) {
                    (sourceFile.statements as unknown as []).splice(j, 1);
                    break;
                }
            }
            // sourceFile.text = sourceFile.text.substring(0,nodeArray[i].pos)+' '.repeat(nodeArray[i].end-nodeArray[i].pos)+sourceFile.text.substring(nodeArray[i].pos);
        }
        // 删除 identifier
        identifiers.forEach((_i)=>{
            if((sourceFile as any as {identifiers:Map<string,string>}).identifiers.has(_i)){
                (sourceFile as any as {identifiers:Map<string,string>}).identifiers.delete(_i);
                // (sourceFile as any as {symbolCount:number}).symbolCount --;
            }
        });
        // 删除 exports
        // if((sourceFile as any).exports){ (sourceFile as any).exports = undefined;  }
        if((sourceFile as any).externalModuleIndicator){(sourceFile as any).externalModuleIndicator = undefined;}
    }
};

// 测试 Wvf
let wvf = new Wvf({
    entry:"HelloWorld.wvf",
    rootDir:"../src/lang-wvf/test",
    outDir:"./www",
    outJS:"index.js",
    outCSS:"index.css",
    resolveImport:{"lodash":{ alias:"_", js:"../doc/lodash.min.js" }},
});
wvf.compile();
